package vpc.ext.stacksize;

import vpc.core.Program;
import vpc.core.Value;
import vpc.core.ProgramDecl;
import vpc.hil.*;
import vpc.hil.parser.Token;
import vpc.ext.stacksize.util.*;
import vpc.ext.stacksize.util.Debug;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
import java.util.HashSet;


public class InterruptMask {	
	
	/*************** DATA *****************/
	private static DeviceDecl device;
	private static Map <Register, Set<Interrupt>> mapReg;
	private static boolean initialized = false;
	private static int numInterrupts = 0;
	
	private final Map<Interrupt, Boolean> mask;
 
	
	
	/*************** CONSTRUCTORS ***************/
		
	public InterruptMask() throws VstackException.ClassInitializationException {
		if (InterruptMask.initialized == false) {
			throw new VstackException.ClassInitializationException("Class InterruptMask not initialized with InterruptMask.init(device).");
		}
		//Object specific initialization here
		this.mask = new HashMap<Interrupt, Boolean>(numInterrupts + 1, (float)1);

		for (Interrupt i : device.interrupts) {
			mask.put(i, false);
		}
		
	}
	
	public InterruptMask(InterruptMask previousMask) {
		mask = previousMask.copyMask();
	}
	
	
	/*************** STATIC CLASS METHODS **************/
	
	private static boolean matchReg(Interrupt i, Register r) {
		if (i == null || r == null) {
			return false;
		}
		String regName = r.getName();
		
		String flagRegString = null;
		if (i.flagReg != null) {
			flagRegString = i.flagReg.toString();
		}
		
		String maskRegString = null;
		if (i.maskReg != null) {
			maskRegString = i.maskReg.toString();
		}
		
		if (regName == null || flagRegString == null || maskRegString == null) {
			return false;
		}
		
		return flagRegString.equals(regName) || maskRegString.equals(regName);
	}
	
	
	public static void init(DeviceDecl device) {
		Debug.Flag localFlag = Debug.Flag.INTR_MASK;
		
		if (InterruptMask.initialized == true) {
			return;
		}
		
		InterruptMask.device = device;
		
		if (InterruptMask.mapReg == null) {
			InterruptMask.mapReg = new HashMap<Register, Set<Interrupt>>();
		}
		
		for (Register r : device.registers) {
			Set<Interrupt> matchingInterrupts = new HashSet<Interrupt>();
			
			for (Interrupt i : device.interrupts) {
				if (matchReg(i, r)) {
					matchingInterrupts.add(i);
				}
			}
			
			if (!matchingInterrupts.isEmpty()) {
				InterruptMask.mapReg.put(r, matchingInterrupts);
			} else {
				matchingInterrupts = null;
			}
		}
				
		InterruptMask.initialized = true;
		
		Debug.println(mapToString(), localFlag);
		
		return;
	}
	
	public static Set<Interrupt> getInterrupts(Register r) {
		return mapReg.get(r);
	}
	
	public static String mapToString() {
		StringBuffer buff = new StringBuffer();
		for (Register r : mapReg.keySet()) {
			buff.append("(reg = " + r.getName() + ", intr = ");
			for (Interrupt i : mapReg.get(r)) {
				buff.append(i.getName() + "/" + DataHandler.bitToBase10(i.maskBit) + ", ");
			}
			buff.append(")\n");
		}
		return buff.toString();
	}
	
	/**
	 * This method calculates the set of interrupts that are enabled
	 * by setting the register with the given constant, raw value.
	 * @param register The register that is being set
	 * @param value A Virgil Value that represents a raw, constant value
	 * @return The non-null, but potentially empty, list of enabled Interrupts.
	 */
	public static Set<Interrupt> getEnabled(Register register, Value value) {
		
		Set<Interrupt> regInterrupts = null;
		Set<Interrupt> enabledInterrupts = new HashSet<Interrupt>();
		
		regInterrupts = InterruptMask.getInterrupts(register);
		if (regInterrupts != null) {

		
		//Get the numerical representation of the constant, raw value
		long bitMask = vpc.core.base.PrimRaw.fromValue(value);
		
		for (Interrupt i : regInterrupts) {
			//TODO : determine if we should be comparing against the
			//flag bit or the mask bit of the interrupt token
			long iMask = DataHandler.bitToBase10(i.maskBit);
						
			if ((bitMask & iMask) != 0) {
				// The resulting mask is non-zero, so the interrupt is enabled
				enabledInterrupts.add(i);
			}
		}
		}
		
		return enabledInterrupts;
	}
	
	private static boolean match(Interrupt i, vpc.core.ProgramDecl.EntryPoint e) {
		return (i.getName().equals(e.getName()));
	}

	/**
	 * Given an interrupt i and a program p, determine the singular Virgil Method
	 * that is designated as the interrupt's handler.
	 * ASSUMPTIONS:
	 * - Each interrupt has exactly one handler
	 * - The name of the entrypoint is the name of the interrupt
	 * @param i The interrupt who's handler we would like to find
	 * @param p The program we are examining
	 * @return A Method representing the interrupt's handler (if found), NULL otherwise.
	 */
	public static vpc.core.decl.Method getInterruptHandler(Interrupt i, Program p) {
		vpc.core.decl.Method handler = null;
		for (vpc.core.ProgramDecl.EntryPoint e : p.programDecl.entryPoints) {
			if (match(i, e)) {
				handler = e.method;
				break;
			}
		}
		return handler;
	}
	
	/*************** OBJECT METHODS **************/
	
	public void enabledAll() {
		for (Interrupt i : this.mask.keySet()) {
			this.enableInterrupt(i);
		}
	}
	
	public boolean isEnabled(Interrupt i) {
		boolean result = false;
		if (this.mask.containsKey(i)) {
			result = this.mask.get(i);
		}
		return result;
	}

	public void enableInterrupt(Interrupt i) {
		if (this.mask.containsKey(i)) {
			this.mask.put(i, true);
		}
		return;
	}
	
	public void disableInterrupt(Interrupt i) {
		if (this.mask.containsKey(i)) {
			this.mask.put(i, false);
		}
		return;
	}
	
	public void enableMaster() {
		return;
	}
	
	public void disableMaster() {
		return;
	}
	
	public String toString() {
		StringBuffer buff = new StringBuffer();
		for (Interrupt i : this.mask.keySet()) {
			boolean enabled = this.mask.get(i);
			if (enabled == true) {
				buff.append("(" + i.getName() + ", enabled)");
			}	
		}
		return buff.toString();
	}
	
	private Map<Interrupt, Boolean> copyMask() {
		Map<Interrupt, Boolean> oldMask = this.mask;
		int length = oldMask.size();
		float loadFactor = 1;
		Map<Interrupt, Boolean> newMask = new HashMap<Interrupt, Boolean>(length, loadFactor);
		for (Interrupt i : oldMask.keySet()) {
			newMask.put(i, oldMask.get(i));
		}
		return newMask;
	}
	
}
